#include <rtthread.h>

#if defined(RT_USING_DEVICE) && defined(RT_USING_I2C)

#include "rtdevice.h"
#include "definitions.h"

#define USE_SERCOM0_I2CM      0
#define USE_SERCOM1_I2CM      0
#define USE_SERCOM2_I2CM      1
#define USE_SERCOM3_I2CM      0
#define USE_SERCOM4_I2CM      0
#define USE_SERCOM5_I2CM      0
#define USE_SERCOM6_I2CM      0

/* samd2x config class */
struct samd2x_i2c_config {
    sercom_registers_t *regs;
    uint32_t clock;
    const char *bus_name;
};

#if (USE_SERCOM2_I2CM > 0)
/* SERCOM2 I2C baud value */
#define SERCOM2_I2CM_BAUD_VALUE         (0x32U)

struct samd2x_i2c_config samd21_i2c2 = {
    .regs = regs,
    .clock = 400000,
    .bus_name = "i2c2"
};
#endif

/* samd2x i2c dirver class */
struct samd2x_i2c {
    struct rt_i2c_ops ops;
    struct rt_i2c_bus_device i2c2_bus;
};

struct samd2x_i2c rt_i2c_dev;

// *****************************************************************************
// *****************************************************************************
// Section: SERCOM2 I2C Implementation
// *****************************************************************************
// *****************************************************************************
// *****************************************************************************
static void SERCOM_I2CM_ClockInit ( void )
{
#if (USE_SERCOM2_I2CM > 0)
    /* Selection of the Generator and write Lock for SERCOM2_CORE */
    GCLK_REGS->GCLK_CLKCTRL = GCLK_CLKCTRL_ID (GCLK_CLKCTRL_ID_SERCOM2_CORE_Val) |
                              GCLK_CLKCTRL_GEN (GCLK_CLKCTRL_GEN_GCLK0_Val)  |
                              GCLK_CLKCTRL_CLKEN_Msk;

    /* Configure the APBC Bridge Clocks */
    PM_REGS->PM_APBCMASK |= PM_APBCMASK_SERCOM (1 << 2);
#endif
}

static void SERCOM_I2CM_PortInit (void)
{
#if (USE_SERCOM2_I2CM > 0)
    PORT_REGS->GROUP[0].PORT_PINCFG[PIN_PA08D_SERCOM2_PAD0] = PORT_PINCFG_PMUXEN (1);
    PORT_REGS->GROUP[0].PORT_PINCFG[PIN_PA09D_SERCOM2_PAD1] = PORT_PINCFG_PMUXEN (1);
#endif
}


static void SERCOM_I2CM_NvicInit ( void )
{
#if (USE_SERCOM2_I2CM > 0)
    NVIC_SetPriority (SERCOM2_IRQn, 3);
    NVIC_EnableIRQ (SERCOM2_IRQn);
#endif
}


void SERCOM_I2C_Initialize ( sercom_registers_t *regs )
{
    /* Reset the module */
    regs->I2CM.SERCOM_CTRLA = SERCOM_I2CM_CTRLA_SWRST_Msk ;

    /* Wait for synchronization */
    while (regs->I2CM.SERCOM_SYNCBUSY);

    /* Enable smart mode enable */
    regs->I2CM.SERCOM_CTRLB = SERCOM_I2CM_CTRLB_SMEN_Msk;

    /* Wait for synchronization */
    while (regs->I2CM.SERCOM_SYNCBUSY);

    /* Baud rate - Master Baud Rate*/
    regs->I2CM.SERCOM_BAUD = SERCOM2_I2CM_BAUD_VALUE;

    /* Set Operation Mode (Master), SDA Hold time, run in stand by and i2c master enable */
    regs->I2CM.SERCOM_CTRLA = SERCOM_I2CM_CTRLA_MODE_I2C_MASTER | SERCOM_I2CM_CTRLA_SDAHOLD_75NS | SERCOM_I2CM_CTRLA_SPEED_STANDARD_AND_FAST_MODE | SERCOM_I2CM_CTRLA_SCLSM (0) | SERCOM_I2CM_CTRLA_ENABLE_Msk ;

    /* Wait for synchronization */
    while (regs->I2CM.SERCOM_SYNCBUSY);

    /* Initial Bus State: IDLE */
    regs->I2CM.SERCOM_STATUS = SERCOM_I2CM_STATUS_BUSSTATE (0x01);

    /* Wait for synchronization */
    while (regs->I2CM.SERCOM_SYNCBUSY);

    /* Enable all Interrupts */
    regs->I2CM.SERCOM_INTENSET = SERCOM_I2CM_INTENSET_Msk;
}

static bool SERCOM_I2C_CalculateBaudValue (uint32_t srcClkFreq, uint32_t i2cClkSpeed, uint32_t *baudVal)
{
    uint32_t baudValue;

    /* Reference clock frequency must be atleast two times the baud rate */
    if (srcClkFreq < (2 * i2cClkSpeed)) {
        return false;
    }

    if (i2cClkSpeed <= 1000000) {
        /* Standard, FM and FM+ baud calculation */
        baudValue = (uint32_t) (((((float)srcClkFreq) / i2cClkSpeed) - ((((float)srcClkFreq) * (200 / 1000000000.0)) + 10)));
    } else {
        return false;
    }
    if (i2cClkSpeed <= 400000) {
        /* For I2C clock speed upto 400 kHz, the value of BAUD<7:0> determines both SCL_L and SCL_H with SCL_L = SCL_H */
        if (baudValue > (0xFF * 2)) {
            /* Set baud rate to the minimum possible value */
            baudValue = 0xFF;
        } else if (baudValue <= 1) {
            /* Baud value cannot be 0. Set baud rate to maximum possible value */
            baudValue = 1;
        } else {
            baudValue /= 2;
        }
    } else {
        /* To maintain the ratio of SCL_L:SCL_H to 2:1, the max value of BAUD_LOW<15:8>:BAUD<7:0> can be 0xFF:0x7F. Hence BAUD_LOW + BAUD can not exceed 255+127 = 382 */
        if (baudValue >= 382) {
            /* Set baud rate to the minimum possible value while maintaining SCL_L:SCL_H to 2:1 */
            baudValue = (0xFF << 8) | (0x7F);
        } else if (baudValue <= 3) {
            /* Baud value cannot be 0. Set baud rate to maximum possible value while maintaining SCL_L:SCL_H to 2:1 */
            baudValue = (2 << 8) | 1;
        } else {
            /* For Fm+ mode, I2C SCL_L:SCL_H to 2:1 */
            baudValue  = ((((baudValue * 2) / 3) << 8) | (baudValue / 3));
        }
    }

    *baudVal = baudValue;
    return true;
}

bool SERCOM_I2C_TransferSetup ( sercom_registers_t *regs, SERCOM_I2C_TRANSFER_SETUP *setup, uint32_t srcClkFreq )
{
    uint32_t baudValue;
    uint32_t i2cClkSpeed;
    uint32_t i2cSpeedMode = 0;

    if (setup == NULL) {
        return false;
    }

    i2cClkSpeed = setup->clkSpeed;

    if ( srcClkFreq == 0) {
        srcClkFreq = 48000000UL;
    }

    if (SERCOM_I2C_CalculateBaudValue (srcClkFreq, i2cClkSpeed, &baudValue) == false) {
        return false;
    }

    if (i2cClkSpeed > 400000) {
        i2cSpeedMode = 1;
    }

    /* Disable the I2C before changing the I2C clock speed */
    regs->I2CM.SERCOM_CTRLA &= ~SERCOM_I2CM_CTRLA_ENABLE_Msk;

    /* Wait for synchronization */
    while (regs->I2CM.SERCOM_SYNCBUSY);

    /* Baud rate - Master Baud Rate*/
    regs->I2CM.SERCOM_BAUD = baudValue;

    regs->I2CM.SERCOM_CTRLA  = ((regs->I2CM.SERCOM_CTRLA & ~SERCOM_I2CM_CTRLA_SPEED_Msk) | (SERCOM_I2CM_CTRLA_SPEED (i2cSpeedMode)));

    /* Re-enable the I2C module */
    regs->I2CM.SERCOM_CTRLA |= SERCOM_I2CM_CTRLA_ENABLE_Msk;

    /* Wait for synchronization */
    while (regs->I2CM.SERCOM_SYNCBUSY);

    /* Since the I2C module was disabled, re-initialize the bus state to IDLE */
    regs->I2CM.SERCOM_STATUS = SERCOM_I2CM_STATUS_BUSSTATE (0x01);

    /* Wait for synchronization */
    while (regs->I2CM.SERCOM_SYNCBUSY);

    return true;
}

static void SERCOM_I2C_InitiateTransfer ( sercom_registers_t *regs, uint16_t address, bool dir)
{
    /* Clear all flags */
    regs->I2CM.SERCOM_INTFLAG = SERCOM_I2CM_INTFLAG_Msk;

    /* Smart mode enabled with SCLSM = 0, - ACK is set to send while receiving the data */
    regs->I2CM.SERCOM_CTRLB &= ~SERCOM_I2CM_CTRLB_ACKACT_Msk;
    /* Wait for synchronization */
    while (regs->I2CM.SERCOM_SYNCBUSY);

    /* Set I2C device address */
    regs->I2CM.SERCOM_ADDR = (address << 1) | dir;
    /* Wait for synchronization */
    while (regs->I2CM.SERCOM_SYNCBUSY);
}

static bool SERCOM_I2C_XferSetup ( sercom_registers_t *regs,
                                   uint16_t address,
                                   uint8_t *wrData,
                                   uint32_t wrLength,
                                   uint8_t *rdData,
                                   uint32_t rdLength,
                                   bool dir,
                                   bool isHighSpeed
                                 )
{
    SERCOM_I2C_InitiateTransfer (regs, address, dir);
    return true;
}

bool SERCOM2_I2C_Read (uint16_t address, uint8_t *rdData, uint32_t rdLength)
{
    return SERCOM2_I2C_XferSetup (address, NULL, 0, rdData, rdLength, I2C_TRANSFER_READ, false);
}

bool SERCOM2_I2C_Write (uint16_t address, uint8_t *wrData, uint32_t wrLength)
{
    return SERCOM2_I2C_XferSetup (address, wrData, wrLength, NULL, 0, I2C_TRANSFER_WRITE, false);
}

bool SERCOM2_I2C_WriteRead (uint16_t address, uint8_t *wrData, uint32_t wrLength, uint8_t *rdData, uint32_t rdLength)
{
    return SERCOM2_I2C_XferSetup (address, wrData, wrLength, rdData, rdLength, I2C_TRANSFER_WRITE, false);
}

#if (USE_SERCOM2_I2CM > 0)
void SERCOM2_I2C_InterruptHandler (void)
{
    if (SERCOM2_REGS->I2CM.SERCOM_INTENSET != 0) {
        /* Checks if the arbitration lost in multi-master scenario */
        if ((SERCOM2_REGS->I2CM.SERCOM_STATUS & SERCOM_I2CM_STATUS_ARBLOST_Msk) == SERCOM_I2CM_STATUS_ARBLOST_Msk) {
            /* Set Error status */
            sercom2I2CObj.state = SERCOM_I2C_STATE_ERROR;
            sercom2I2CObj.error = SERCOM_I2C_ERROR_BUS;

        }
        /* Check for Bus Error during transmission */
        else if ((SERCOM2_REGS->I2CM.SERCOM_STATUS & SERCOM_I2CM_STATUS_BUSERR_Msk) == SERCOM_I2CM_STATUS_BUSERR_Msk) {
            /* Set Error status */
            sercom2I2CObj.state = SERCOM_I2C_STATE_ERROR;
            sercom2I2CObj.error = SERCOM_I2C_ERROR_BUS;
        }
        /* Checks slave acknowledge for address or data */
        else if ((SERCOM2_REGS->I2CM.SERCOM_STATUS & SERCOM_I2CM_STATUS_RXNACK_Msk) == SERCOM_I2CM_STATUS_RXNACK_Msk) {
            sercom2I2CObj.state = SERCOM_I2C_STATE_ERROR;
            sercom2I2CObj.error = SERCOM_I2C_ERROR_NAK;
        } else {
            switch (sercom2I2CObj.state) {
                case SERCOM_I2C_REINITIATE_TRANSFER:

                    if (sercom2I2CObj.writeSize != 0) {
                        /* Initiate Write transfer */
                        SERCOM2_I2C_InitiateTransfer (sercom2I2CObj.address, false);
                    } else {
                        /* Initiate Read transfer */
                        SERCOM2_I2C_InitiateTransfer (sercom2I2CObj.address, true);
                    }

                    break;


                case SERCOM_I2C_STATE_IDLE:

                    break;



                case SERCOM_I2C_STATE_TRANSFER_WRITE:

                    if (sercom2I2CObj.writeCount == (sercom2I2CObj.writeSize)) {
                        if (sercom2I2CObj.readSize != 0) {

                            /* Write 7bit address with direction (ADDR.ADDR[0]) equal to 1*/
                            SERCOM2_REGS->I2CM.SERCOM_ADDR =  (sercom2I2CObj.address << 1) | I2C_TRANSFER_READ;

                            /* Wait for synchronization */
                            while (SERCOM2_REGS->I2CM.SERCOM_SYNCBUSY);

                            sercom2I2CObj.state = SERCOM_I2C_STATE_TRANSFER_READ;

                        } else {
                            SERCOM2_REGS->I2CM.SERCOM_CTRLB |= SERCOM_I2CM_CTRLB_CMD (3);

                            /* Wait for synchronization */
                            while (SERCOM2_REGS->I2CM.SERCOM_SYNCBUSY);

                            sercom2I2CObj.state = SERCOM_I2C_STATE_TRANSFER_DONE;
                        }
                    }
                    /* Write next byte */
                    else {
                        SERCOM2_REGS->I2CM.SERCOM_DATA = sercom2I2CObj.writeBuffer[sercom2I2CObj.writeCount++];
                    }

                    break;

                case SERCOM_I2C_STATE_TRANSFER_READ:

                    if (sercom2I2CObj.readCount == (sercom2I2CObj.readSize - 1)) {
                        /* Set NACK and send stop condition to the slave from master */
                        SERCOM2_REGS->I2CM.SERCOM_CTRLB |= SERCOM_I2CM_CTRLB_ACKACT_Msk | SERCOM_I2CM_CTRLB_CMD (3);

                        /* Wait for synchronization */
                        while (SERCOM2_REGS->I2CM.SERCOM_SYNCBUSY);

                        sercom2I2CObj.state = SERCOM_I2C_STATE_TRANSFER_DONE;
                    }

                    /* Read the received data */
                    sercom2I2CObj.readBuffer[sercom2I2CObj.readCount++] = SERCOM2_REGS->I2CM.SERCOM_DATA;


                    break;

                default:

                    break;
            }
        }

        /* Error Status */
        if (sercom2I2CObj.state == SERCOM_I2C_STATE_ERROR) {
            /* Reset the PLib objects and Interrupts */
            sercom2I2CObj.state = SERCOM_I2C_STATE_IDLE;

            /* Generate STOP condition */
            SERCOM2_REGS->I2CM.SERCOM_CTRLB |= SERCOM_I2CM_CTRLB_CMD (3);

            /* Wait for synchronization */
            while (SERCOM2_REGS->I2CM.SERCOM_SYNCBUSY);

            SERCOM2_REGS->I2CM.SERCOM_INTFLAG = SERCOM_I2CM_INTFLAG_Msk;

            if (sercom2I2CObj.callback != NULL) {
                sercom2I2CObj.callback (sercom2I2CObj.context);
            }
        }
        /* Transfer Complete */
        else if (sercom2I2CObj.state == SERCOM_I2C_STATE_TRANSFER_DONE) {
            /* Reset the PLib objects and interrupts */
            sercom2I2CObj.state = SERCOM_I2C_STATE_IDLE;
            sercom2I2CObj.error = SERCOM_I2C_ERROR_NONE;

            SERCOM2_REGS->I2CM.SERCOM_INTFLAG = SERCOM_I2CM_INTFLAG_Msk;

            /* Wait for the NAK and STOP bit to be transmitted out and I2C state machine to rest in IDLE state */
            while ((SERCOM2_REGS->I2CM.SERCOM_STATUS & SERCOM_I2CM_STATUS_BUSSTATE_Msk) != SERCOM_I2CM_STATUS_BUSSTATE (0x01));

            if (sercom2I2CObj.callback != NULL) {
                sercom2I2CObj.callback (sercom2I2CObj.context);
            }

        }
    }

    return;
}
#endif

rt_size_t samd2x_master_xfer (struct rt_i2c_bus_device *bus,
                              struct rt_i2c_msg msgs[],
                              rt_uint32_t num)
{

}

struct rt_i2c_bus_device_ops samd2x_i2c_ops = {
    samd2x_master_xfer,
    RT_NULL,
    RT_NULL
};

int rtt_i2c_init (void)
{
    struct samd2x_i2c_config config = RT_I2CM_CONFIG_DEFAULT;

    SERCOM_I2C_Initialize (samd21_i2c2.regs);

    config.bufsz = 256;
    _serial3.config = config;
    _serial3.ops = &_uart_ops;

    return rt_i2c_bus_device_device_init (&_serial3, "i2c2", RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX, &_uart3); //
}
INIT_DEVICE_EXPORT (rtt_i2c_init);

#endif
